﻿using System;
using System.Collections.Generic;
using System.Linq;
using Pyramid.Hardware.Gates;

namespace Pyramid.Hardware
{
	public class BusMultiplexer
	{
		private ILine[] _outputBus;

		public BusMultiplexer(ILine selectorLine, IEnumerable<ILine> inputBus)
			: this(selectorLine, inputBus.ToArray())
		{
		}

		public BusMultiplexer(ILine selectorLine, params ILine[] inputBus)
			: this(new[] { selectorLine }, inputBus)
		{
		}

		public BusMultiplexer(IEnumerable<ILine> selectorBus, IEnumerable<IEnumerable<ILine>> inputBuses)
			: this(selectorBus, inputBuses.ToArray())
		{
		}

		public BusMultiplexer(IEnumerable<ILine> selectorBus, params IEnumerable<ILine>[] inputBuses)
		{
			int busCount = inputBuses.Length;

			int busSize = inputBuses[0].Count();

			foreach (var input in inputBuses)
			{
				if (input.Count() != busSize)
				{
					throw new ArgumentException("Input buses must all be of the same size.", "inputBuses");
				}
			}

			var decoder = new MultilineDecoder(selectorBus);

			_outputBus = new ILine[busSize];
			ILine[] decodedMaskLines = decoder.OutputBus.ToArray();
			AndGate[,] masks = new AndGate[busCount, busSize];

			for (int busIndex = 0; busIndex < busCount; busIndex++)
			{
				ILine[] inputBus = inputBuses[busIndex].ToArray();

				for (int lineIndex = 0; lineIndex < busSize; lineIndex++)
					masks[busIndex, lineIndex] = new AndGate(decodedMaskLines[busIndex], inputBus[lineIndex]);
			}


			for (int lineIndex = 0; lineIndex < busSize; lineIndex++)
			{
				OrGate lastGate = null;

				//Note that we are starting at busIndex 1 as n buses require n-1
				//Or gates
				for (int busIndex = 1; busIndex < busCount; busIndex++)
					if (lastGate == null)
						lastGate = new OrGate(masks[busIndex - 1, lineIndex], masks[busIndex, lineIndex]);
					else
						lastGate = new OrGate(lastGate, masks[busIndex, lineIndex]);

				_outputBus[lineIndex] = lastGate;
			}
		}

		public IEnumerable<ILine> OutputBus
		{
			get
			{
				return _outputBus;
			}
		}
	}
}
